using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using BostonXNA.InputHandling.Input;
using BostonXNA.FiniteStateMachine;
using System.Collections.ObjectModel;

namespace BostonXNA.InputHandling
{
	/// <summary>
	/// This is the main type for your game
	/// </summary>
	public class Game1 : Microsoft.Xna.Framework.Game
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;

		Texture2D crosshair;
		float zoom = 1.0f;
		SoundEffect clap;
		Vector2 position;

		InputHandling.Input.InputHandler input;

		public Game1()
		{
			graphics = new GraphicsDeviceManager( this );

			Content.RootDirectory = "Content";
#if !WINDOWSPHONE
			graphics.PreferredBackBufferWidth = 1280;
			graphics.PreferredBackBufferHeight = 720;
#else
			graphics.PreferredBackBufferWidth = 480;
			graphics.PreferredBackBufferHeight = 800;  
#endif
			input = new Input.InputHandler( this.Window );
		}

		/// <summary>
		/// Allows the game to perform any initialization it needs to before starting to run.
		/// This is where it can query for any required services and load any non-graphic
		/// related content.  Calling base.Initialize will enumerate through any components
		/// and initialize them as well.
		/// </summary>
		protected override void Initialize()
		{
			// TODO: Add your initialization logic here
			position = new Vector2( 0, 0 );

			initializeFSM();

			input.Initialize();

			var testSequence = new InputSequence();
			testSequence.Add( new IInputActivity[] 
			{ 
				new KeyDownActivity() { Key = Keys.LeftControl }, 
				new KeyDownActivity() { Key = Keys.Space } 
			} );

			input.AddAction( FireWeapon, testSequence );

			input.AddAction( MoveLeft, Keys.Left );
			input.AddAction( MoveRight, Keys.Right );
			input.AddAction( MoveUp, Keys.Up );
			input.AddAction( MoveDown, Keys.Down );
			input.AddAction( MouseMoved, MouseMove.Left );
			input.AddAction( MouseMoved, MouseMove.Right );
			input.AddAction( MouseMoved, MouseMove.Up );
			input.AddAction( MouseMoved, MouseMove.Down );

			input.AddAction( MoveLeft, Keys.A );
			input.AddAction( MoveRight, Keys.D );
			input.AddAction( MoveUp, Keys.W );
			input.AddAction( MoveDown, Keys.S );

			var leftDown = new InputSequence();
			leftDown.Add( new IInputActivity[] 
			{ 
				new GamePadButtonDownActivity() { Button = Buttons.LeftStick }
			} );
			input.AddAction( ZoomIn, leftDown );
			//var leftUp = new InputSequence();
			//leftUp.Add( new IInputActivity[] 
			//{ 
			//    new GamePadButtonUpActivity() { Button = Buttons.LeftStick }
			//} );
			//input.AddAction( ZoomOut, leftUp );
			input.AddAction( Quit, Keys.Escape );
			input.AddAction( Quit, Buttons.Back );

			var thumbSequence = new InputSequence();
			thumbSequence.Add( new IInputActivity[] 
			{ 
				new GamePadThumbstickActivity() { ThumbStick = ThumbStick.Left }
			} );

			input.AddAction( LeftThumb, thumbSequence );
			base.Initialize();
		}

		class NoopFiniteStateMachine : IFiniteStateMachine<object>
		{
			public event EventHandler BeforeStateChange;

			public event EventHandler StateChanging;

			public event EventHandler AfterStateChange;

			public IState CurrentState
			{
				get;
				set;
			}

			public IEnumerable<IState> States
			{
				get;
				set;
			}

			public IEnumerable<ITransition<object>> Transitions
			{
				get;
				set;
			}

			public void Update( object stateEvent )
			{
				if ( CurrentState == null )
				{
					var initTransition = from t in Transitions
										 where t.SourceState == null
										 select t;

					if ( initTransition.Any() )
					{
						CurrentState = initTransition.First().DestinationState;
						CurrentState.Enter( null );
					}
					else
					{
						throw new Exception( "No initial state identified." );
					}
				}
				else
				{
					var transitions = from t in Transitions
									  where t.SourceState == CurrentState
									  select t;
					transitions.ToList().ForEach( ( transition ) =>
					{
						if ( transition.SourceState != CurrentState )
							return;

						if ( transition.GuardCondition( stateEvent ) )
						{
							CurrentState.Leave( null );
							CurrentState = transition.DestinationState;
							CurrentState.Enter( null );
						}
					} );
				}
			}
		}
		class KeyFiniteStateMachine : IFiniteStateMachine<IKeyEvent>
		{
			public event EventHandler BeforeStateChange;

			public event EventHandler StateChanging;

			public event EventHandler AfterStateChange;

			public IState CurrentState
			{
				get;
				set;
			}

			public IEnumerable<IState> States
			{
				get;
				set;
			}

			public IEnumerable<ITransition<IKeyEvent>> Transitions
			{
				get;
				set;
			}

			public void Update( IKeyEvent stateEvent )
			{
				if ( CurrentState == null )
				{
					var initTransition = from t in Transitions
										 where t.SourceState == null
										 select t;

					if ( initTransition.Any() )
					{
						CurrentState = initTransition.First().DestinationState;
						CurrentState.Enter( null );
					}
					else
					{
						throw new Exception( "No initial state identified." );
					}
				}
				else
				{
					var transitions = from t in Transitions
									  where t.SourceState == CurrentState
									  select t;
					transitions.ToList().ForEach( ( transition ) =>
					{
						if ( transition.SourceState != CurrentState )
							return;

						if ( transition.GuardCondition( stateEvent ) )
						{
							CurrentState.Leave( null );
							CurrentState = transition.DestinationState;
							CurrentState.Enter( null );
						}
					} );
				}
			}
		}

		class BasicState : IState
		{
			public Action<EventArgs> Enter
			{
				get;
				set;
			}

			public Action<EventArgs> Leave
			{
				get;
				set;
			}
		}

		class SimpleTransition : ITransition<object>
		{
			public IState SourceState
			{
				get;
				set;
			}

			public IState DestinationState
			{
				get;
				set;
			}

			public Predicate<object> GuardCondition
			{
				get;
				set;
			}
		}

		class KeyPressTransition : ITransition<IKeyEvent>
		{
			public IState SourceState
			{
				get;
				set;
			}

			public IState DestinationState
			{
				get;
				set;
			}

			public Predicate<IKeyEvent> GuardCondition
			{
				get;
				set;
			}
		}

		KeyFiniteStateMachine ism = new KeyFiniteStateMachine();

		private void initializeFSM()
		{
			//            #region
			//            IFiniteStateMachine<object> fsm = new NoopFiniteStateMachine();
			//            System.Collections.Generic.Stack<object> bummer;

			//            fsm.States = new List<IState>()
			//                {
			//                    new BasicState()
			//                    {
			//                        Enter = ( e ) => { Console.WriteLine( "Entered Init State."); },
			//                        Leave = ( e ) => { Console.WriteLine( "Leaving Init State."); }
			//                    },

			//                    new BasicState() 					
			//                    {
			//                        Enter = ( e ) => { Console.WriteLine( "Entered Start State."); },
			//                        Leave = ( e ) => { Console.WriteLine( "Leaving Start State."); }
			//                    },

			//                    new BasicState() 					
			//                    {
			//                        Enter = ( e ) => { Console.WriteLine( "Entered MainMenu State."); },
			//                        Leave = ( e ) => { Console.WriteLine( "Leaving MainMenu State."); }
			//                    },

			//                };
			//            fsm.Transitions = new List<ITransition<object>>()
			//                {
			//                    new SimpleTransition()
			//                    {
			//                        SourceState = null,
			//                        DestinationState = ((IList<IState>)fsm.States)[0],
			//                        GuardCondition = ( o ) => { return true; }
			//                    },
			//                    new SimpleTransition()
			//                    {
			//                        SourceState = ((IList<IState>)fsm.States)[0],
			//                        DestinationState =  ((IList<IState>)fsm.States)[1],
			//                        GuardCondition = ( o ) => { return (String)o == "Start"; }
			//                    },
			//                    new SimpleTransition()
			//                    {
			//                        SourceState =  ((IList<IState>)fsm.States)[1],
			//                        DestinationState =  ((IList<IState>)fsm.States)[2],
			//                        GuardCondition = ( o ) => { return (String)o == "Pause"; }
			//                    },
			//                };

			//            fsm.Update( null );
			//            fsm.Update( "Start" );
			//            fsm.Update( "Pause" );
			//#endregion

			ism.States = new List<IState> {
				new BasicState() // Init
				{
					Enter = ( e ) => {},
					Leave = ( e ) => {}
				},
				new BasicState() // Down
				{										
					Enter = ( e ) => {},
					Leave = ( e ) => {}
				},
				new BasicState() // Up
				{
					Enter = ( e ) => 
					{ 
						// Fire the Discombulator!
						FireWeapon( PlayerIndex.One, null );
						Console.WriteLine( "Where's the kaboom?" );
					},
					Leave = ( e ) => {}
				}
			};

			ism.Transitions = new List<ITransition<IKeyEvent>>()
			{
				new KeyPressTransition()
				{
					SourceState = null,
					DestinationState = ((IList<IState>)ism.States)[0],
					GuardCondition = ( o ) => true
				},
				new KeyPressTransition()
				{
					SourceState = ((IList<IState>)ism.States)[0],
					DestinationState = ((IList<IState>)ism.States)[1],
					GuardCondition = ( o ) => o.Key.IsKeyDown( Keys.LeftControl ) && o.Key.IsKeyDown( Keys.Space )
				},
				new KeyPressTransition()
				{
					SourceState = ((IList<IState>)ism.States)[1],
					DestinationState = ((IList<IState>)ism.States)[2],
					GuardCondition = ( o ) => o.Key.IsKeyUp( Keys.LeftControl ) || o.Key.IsKeyUp( Keys.Space )
				},
				new KeyPressTransition()
				{
					SourceState = ((IList<IState>)ism.States)[2],
					DestinationState = ((IList<IState>)ism.States)[1],
					GuardCondition = ( o ) => o.Key.IsKeyDown( Keys.LeftControl ) && o.Key.IsKeyDown( Keys.Space )
				},


			};
		}

		/// <summary>
		/// LoadContent will be called once per game and is the place to load
		/// all of your content.
		/// </summary>
		protected override void LoadContent()
		{
			// Create a new SpriteBatch, which can be used to draw textures.
			spriteBatch = new SpriteBatch( GraphicsDevice );

			// TODO: use this.Content to load your game content here
			crosshair = Content.Load<Texture2D>( "crosshair" );
			clap = Content.Load<SoundEffect>( "clap" );
		}

		/// <summary>
		/// UnloadContent will be called once per game and is the place to unload
		/// all content.
		/// </summary>
		protected override void UnloadContent()
		{
			// TODO: Unload any non ContentManager content here
		}

		/// <summary>
		/// Allows the game to run logic such as updating the world,
		/// checking for collisions, gathering input, and playing audio.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Update( GameTime gameTime )
		{

			input.Update();

			var keyState = Keyboard.GetState();
			
			ism.Update( new KeyPressEvent( keyState ) );

			base.Update( gameTime );
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Draw( GameTime gameTime )
		{
			GraphicsDevice.Clear( Color.CornflowerBlue );

			// TODO: Add your drawing code here
			spriteBatch.Begin();
			int size = (int)( 64f * zoom );
			spriteBatch.Draw( crosshair, new Rectangle( (int)position.X, (int)position.Y, size, size ), Color.White );
			spriteBatch.End();

			base.Draw( gameTime );
		}

		public void MouseMoved( PlayerIndex player, InputState state )
		{
			position.X = state.Mouse.Current.X;
			position.Y = state.Mouse.Current.Y;
		}
		public void LeftThumb( PlayerIndex player, InputState state )
		{
			position.X += 5 * state.GamePad.Current.ThumbSticks.Left.X;
			position.Y += -5 * state.GamePad.Current.ThumbSticks.Left.Y;
		}
		public void MoveLeft( PlayerIndex player, InputState state )
		{
			position.X -= 1;
		}
		public void MoveRight( PlayerIndex player, InputState state )
		{
			position.X += 1;
		}
		public void MoveUp( PlayerIndex player, InputState state )
		{
			position.Y -= 1;
		}
		public void MoveDown( PlayerIndex player, InputState state )
		{
			position.Y += 1;
		}
		public void FireWeapon( PlayerIndex player, InputState state )
		{
			clap.Play();
		}
		public void Quit( PlayerIndex player, InputState state )
		{
			this.Exit();
		}
		public void ZoomIn( PlayerIndex player, InputState state )
		{
			if ( zoom == 2.0f )
				zoom = 1.0f;
			else
				zoom = 2.0f;
		}
		public void ZoomOut( PlayerIndex player, InputState state )
		{
			zoom = 1.0f;
		}
	}
}
